package com.yhk.wuchat.common.websocket.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.yhk.wuchat.common.common.config.ThreadPoolConfig;
import com.yhk.wuchat.common.common.event.UserOnlineEvent;
import com.yhk.wuchat.common.user.dao.UserDao;
import com.yhk.wuchat.common.user.domain.entity.IpInfo;
import com.yhk.wuchat.common.user.domain.entity.User;
import com.yhk.wuchat.common.user.domain.enums.RoleEnum;
import com.yhk.wuchat.common.user.service.IRoleService;
import com.yhk.wuchat.common.user.service.IUserRoleService;
import com.yhk.wuchat.common.user.service.LoginService;
import com.yhk.wuchat.common.websocket.NettyUtil;
import com.yhk.wuchat.common.websocket.domain.dto.WSChannelExtraDTO;
import com.yhk.wuchat.common.websocket.domain.vo.resp.WSBaseResp;
import com.yhk.wuchat.common.websocket.service.WebSocketService;
import com.yhk.wuchat.common.websocket.service.adapter.WebSocketAdapter;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @version 1.0
 * @Author moresuo
 * @Date 2024/4/2 16:52
 * @注释
 */
@Service
@Slf4j
public class WebSocketServiceImpl implements WebSocketService {
    @Resource
    @Lazy
    private WxMpService wxMpService;
    @Resource
    private UserDao userDao;

    @Resource
    private LoginService loginService;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Resource
    private IRoleService roleService;
    @Resource
    @Qualifier(ThreadPoolConfig.WS_EXECUTOR)
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    //管理所有用户的连接，包括登录态和游客
    private static final ConcurrentHashMap<Channel, WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();
    public static final Duration DURATION = Duration.ofHours(1);
    public static final int MAXIMUM_SIZE=1000;
    //保存登录事件码与channel的映射
    private static final Cache<Integer,Channel> WAIT_LOGIN_MAP= Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE)//最大容量，超过就淘汰
            .expireAfterWrite(DURATION)//设置过期时间
            .build();

    /**
     * 用户连接
     * @param channel
     */
    @Override
    public void connect(Channel channel) {
        //用户上线,保存用户连接信息，方便后续进行管理
        ONLINE_WS_MAP.put(channel, new WSChannelExtraDTO());
    }


    /**
     * 前端扫码登录请求
     * @param channel
     */
    @SneakyThrows
    @Override
    public void handlerLoginReq(Channel channel) {
        //生成事件码，并保存code与channel的映射
        Integer code = generateLoginCode(channel);
        //找微信申请二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());
        log.info("生成的二维码信息：{}", wxMpQrCodeTicket.getUrl());
        //把码推送给前端
        sendMsg(channel, WebSocketAdapter.buildResp(wxMpQrCodeTicket));
    }

    /**
     * 用户下线，移除连接
     * @param channel
     */
    @Override
    public void remove(Channel channel) {
        //用户下线
        ONLINE_WS_MAP.remove(channel);
        //todo 用户下线

    }

    /**
     * 用户扫码登录成功事件
     * @param code
     * @param id
     */
    @Override
    public void scanLoginSuccess(Integer code, Long id) {
        //确认连接在机器上
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if(Objects.isNull(channel)){
            log.warn("连接已失效");
            return;
        }
        //获取最新的用户信息返回给前端
        User user = userDao.getById(id);
        //移除code
        WAIT_LOGIN_MAP.invalidate(code);
        //调用登录模块获取token
        String token = loginService.login(id);
        log.info("用户token,{}",token);
        //登录成功
        loginSuccess(channel,user,token);
    }

    /**
     * 等待用户授权
     * @param fromUser
     * @param code
     */
    @Override
    public void waitAuthorize(String fromUser, Integer code) {
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if(Objects.isNull(channel)){
            return;
        }
        sendMsg(channel, WebSocketAdapter.buildWaitAuthorizeResp());
    }

    /**
     * 用户登录认证
     * @param channel
     * @param token
     */
    @Override
    public void authorize(Channel channel, String token) {
        //根据token解析出有效的uid
        Long uid = loginService.getValidUid(token);
        if(Objects.isNull(uid)){
            //Token失效让用户重新登陆
            sendMsg(channel,WebSocketAdapter.buildInvalidTokenResp());
        }else{
            //拿到用户id获取连接
            User user = userDao.getById(uid);
            //用户登录成功
            loginSuccess(channel,user,token);
        }
    }

    @Override
    public void sendMsgToAll(WSBaseResp<?> msg) {
        ONLINE_WS_MAP.forEach((channel,ext)->{
            threadPoolTaskExecutor.execute(()->sendMsg(channel,msg));
        });
    }

    /**
     * 身份认证成功执行登录成功操作
     * @param channel
     * @param user
     * @param token
     */
    private void loginSuccess(Channel channel, User user, String token) {
        //保存channel对应的uid
        WSChannelExtraDTO wsChannelExtraDTO = ONLINE_WS_MAP.get(channel);
        wsChannelExtraDTO.setUid(user.getId());
        //消息推送成功
        sendMsg(channel,WebSocketAdapter.buildResp(user,token,roleService.hasPower(user.getId(), RoleEnum.CHAT_MANAGER)));
        // 用户上线成功的事件变更
        user.setLastOptTime(LocalDateTime.now());
        user.refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP));
        //发布用户上线事件
        applicationEventPublisher.publishEvent(new UserOnlineEvent(this, user));
    }

    /**
     * 向前端返回封装后的数据
     * @param channel
     * @param wsBaseResp
     */
    private void sendMsg(Channel channel, WSBaseResp<?> wsBaseResp) {
        //以帧的形式返回消息
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(wsBaseResp)));
    }

    /**
     * 生成随机码
     * @param channel
     * @return
     */
    private Integer generateLoginCode(Channel channel) {
        Integer code;
        do {
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        } while (Objects.nonNull(WAIT_LOGIN_MAP.asMap().putIfAbsent(code, channel)));
        return code;
    }
}
